/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.map;

import com.google.common.collect.ImmutableMap;
import genomemap.map.ContigMap.Contig;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * It uses <code>MinimalContigMap</code>.
 *
 * @author Susanta Tewari
 * @version 1.0 December, 2008
 * @see MinimalContigMap
 */
public class RepeatsDistMapPanel extends JPanel {

    /** Field description */
    private static final long serialVersionUID = 1L;


    // <editor-fold defaultstate="collapsed" desc="Image Parameters">

    /** Field description */
    private int MAP_HEADER_VERT_INDENT = 40;

    /** Field description */
    private int MAP_HEADER_SCALE_GAP = 10;

    /** Field description */
    private int MAP_SCALE_HORI_INDENT = 10;


    // keep it multiple of 2

    /** Field description */
    private int SCALE_TEXT_LINE_GAP = 12;


    // make it a multiple of 12 * 5 * int( each 200 kb gap)

    /** Field description */
    private int MAX_CH_PIX_SIZE = 600;

    /** Field description */
    private int INTER_CHROMOSOME_GAP = 50;

    /** Field description */
    private int CONTIG_HEIGHT = 15;

    /** Field description */
    private int MAP_LEGEND_HORI_GAP = -45;

    /** Field description */
    private int LEGEND_WIDTH = 15;

    /** Field description */
    private int LEGEND_FIG_TEXT_GAP = 15;

    /** Field description */
    private int LEGEND_HORI_PAD = 10;

    /** Field description */
    private int MAX_CH_SIZE_MB = 12;

    /** Field description */
    private Stroke lineStroke = new BasicStroke(2.5f, BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND);

    /** Field description */
    private Font textFont = new Font("Courier New", Font.BOLD, 12);


    // input

    /** Field description */
    private String mapHeader = null;


    // </editor-fold>

    /** Field description */
    private int mapWIDTH = 0;

    /** Field description */
    private int mapHEIGHT = 0;

    /** Field description */
    private int probeSize = 34;

    /** Field description */
    private List<ContigMap> contigMaps;

    /** Field description */
    private ImmutableMap<String, Integer> cloneRepeats;

    /**
     * @param mapHeader map header
     * @param probeMaps probe maps
     * @param probeSize probe size
     * @param cloneRepeats clone with their repeats count
     */
    @SuppressWarnings("TypeMayBeWeakened")
    public RepeatsDistMapPanel(final String mapHeader, final List<ProbeMap> probeMaps,
                               final int probeSize, final Map<String, Integer> cloneRepeats) {

        this.mapHeader    = mapHeader;
        this.probeSize    = probeSize;
        this.cloneRepeats = ImmutableMap.copyOf(cloneRepeats);
        contigMaps        = new ArrayList<>(probeMaps.size());

        for (final ProbeMap probeMap : probeMaps) {
            contigMaps.add(new MinimalContigMap(probeMap));
        }

        init();
    }

    /**
     * initialize the size of the graphics
     */
    private void init() {

        this.mapHEIGHT = 2 * MAP_HEADER_VERT_INDENT + this.getFontMetrics(textFont).getHeight()
                         + MAP_HEADER_SCALE_GAP
                         + contigMaps.size() * (INTER_CHROMOSOME_GAP + CONTIG_HEIGHT);
        this.mapWIDTH = 2 * MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP + MAX_CH_PIX_SIZE
                        + MAP_LEGEND_HORI_GAP + LEGEND_FIG_TEXT_GAP + LEGEND_WIDTH
                        + 2 * LEGEND_HORI_PAD
                        + 2 * this.getFontMetrics(textFont).stringWidth("VII");

        setPreferredSize(new Dimension(mapWIDTH, mapHEIGHT));
        setBackground(Color.white);
        setName(mapHeader);
    }

    /**
     * Method description
     *
     *
     * @param g
     */
    @Override
    public void paintComponent(final Graphics g) {

        super.paintComponent(g);

        final Graphics2D g2 = (Graphics2D) g;

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setStroke(lineStroke);


        // create background
        g2.setColor(Color.white);
        g2.fillRect(0, 0, mapWIDTH, mapHEIGHT);


        // set up text color and font and rendering hints
        g2.setColor(Color.black);
        g2.setFont(textFont);


        // map header

        /* Field description */
        final int MAP_HEADER_HORI_INDENT = 40;

        g2.drawString(mapHeader, MAP_HEADER_HORI_INDENT, MAP_HEADER_VERT_INDENT);


        // scales : horizontal (12 MB divide in MB`s and each MB divided in
        // quarters)
        final int horiScaleTextY = MAP_HEADER_VERT_INDENT + getFontMetrics(textFont).getHeight()
                                   + MAP_HEADER_SCALE_GAP;

        for (int mbIndex = 0; mbIndex <= MAX_CH_SIZE_MB; mbIndex++) {

            final String label = "" + mbIndex;
            final int x = (int) (MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP
                                 + ((double) MAX_CH_PIX_SIZE / MAX_CH_SIZE_MB) * mbIndex);

            g2.drawString(label, x, horiScaleTextY);


            // bigger line - scale
            g2.drawLine(x, horiScaleTextY + SCALE_TEXT_LINE_GAP / 6, x,
                        horiScaleTextY + SCALE_TEXT_LINE_GAP);


            // smaller line - scale
            if (mbIndex < MAX_CH_SIZE_MB) {

                for (int subIndex = 1; subIndex < 5; subIndex++) {

                    final int subX = x + (MAX_CH_PIX_SIZE / 60) * subIndex;

                    g2.drawLine(subX, horiScaleTextY + SCALE_TEXT_LINE_GAP / 2, subX,
                                horiScaleTextY + SCALE_TEXT_LINE_GAP);
                }
            }
        }

        g2.drawLine(MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP,
                    horiScaleTextY + SCALE_TEXT_LINE_GAP,
                    MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP + MAX_CH_PIX_SIZE,
                    horiScaleTextY + SCALE_TEXT_LINE_GAP);


        // scale: vertical
        for (int i = 1; i <= contigMaps.size(); i++) {

            final String label = "" + i;
            final int y        = horiScaleTextY + SCALE_TEXT_LINE_GAP + INTER_CHROMOSOME_GAP * i;
            final int x = MAP_SCALE_HORI_INDENT - this.getFontMetrics(textFont).stringWidth(label);

            g2.drawString(label, x, y);
        }

        g2.drawLine(MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP,
                    horiScaleTextY + SCALE_TEXT_LINE_GAP,
                    MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP,
                    horiScaleTextY + SCALE_TEXT_LINE_GAP + 8 * INTER_CHROMOSOME_GAP);


        // draw contigs
        for (int i = 0; i < contigMaps.size(); i++) {

            int x = MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP;


            // offset is used to align with chromosome numbers
            final int y = horiScaleTextY + SCALE_TEXT_LINE_GAP + INTER_CHROMOSOME_GAP * (i + 1)
                          - this.getFontMetrics(textFont).getHeight();
            final int CLONE_PIXEL_WIDTH = getMapPixelValue(probeSize);
            final ContigMap contigMap   = contigMaps.get(i);

            for (Iterator<Contig> itr = contigMap.iterator(); itr.hasNext(); ) {

                final Contig contig      = itr.next();
                final int contigPixWidth = getMapPixelValue(contig.getSize());


                // an enclosing rectangle for the contig
                g2.setColor(Color.black);
                g2.drawRect(x, y, contigPixWidth, CONTIG_HEIGHT);


                // draw probes and linking clones with colors
                for (final String clone : contig.getClones()) {

                    g2.setColor(getCloneColor(clone));
                    g2.drawRect(x, y, CLONE_PIXEL_WIDTH, CONTIG_HEIGHT);

                    x += CLONE_PIXEL_WIDTH;
                }

                if (itr.hasNext()) {
                    x += getMapPixelValue(contigMap.contigGap(contig).intValue());
                }
            }

            g2.setColor(Color.black);
            g2.drawString("" + contigMap.getContigCount() + "/" + contigMap.getProbes().size(),
                          x - 25, y - 5);
        }


        // map legend
        final int legend_box_x = MAP_SCALE_HORI_INDENT + SCALE_TEXT_LINE_GAP + MAX_CH_PIX_SIZE
                                 + MAP_LEGEND_HORI_GAP;

        /* Field description */
        final int MAP_LEGEND_VERT_GAP = 30;
        final int legend_box_y = MAP_HEADER_VERT_INDENT + this.getFontMetrics(textFont).getHeight()
                                 + MAP_HEADER_SCALE_GAP + SCALE_TEXT_LINE_GAP + MAP_LEGEND_VERT_GAP;
        final int legend_box_width = 2 * LEGEND_HORI_PAD + LEGEND_FIG_TEXT_GAP
                                     + this.getFontMetrics(textFont).stringWidth("7")
                                     + LEGEND_WIDTH;

        /* Field description */
        final int LEGEND_VERT_PAD  = 10;    /* Field description */
        final int INTER_LEGEND_GAP = 20;    /* Field description */
        final int LEGEND_HEIGHT    = 15;
        final int legend_box_height = 2 * LEGEND_VERT_PAD + contigMaps.size() * LEGEND_HEIGHT
                                      + (contigMaps.size() - 1) * INTER_LEGEND_GAP;

        g2.setColor(Color.black);
        g2.drawRect(legend_box_x, legend_box_y, legend_box_width, legend_box_height);

        int legend_y         = legend_box_y + LEGEND_VERT_PAD;
        final int fontHeight = this.getFontMetrics(textFont).getHeight();

        for (int i = 1; i <= contigMaps.size(); i++) {

            g2.setColor(Color.black);
            g2.drawString("" + i, legend_box_x + LEGEND_HORI_PAD, legend_y + fontHeight);
            g2.setColor(getCloneColor(i));
            g2.fillRect(legend_box_x + LEGEND_HORI_PAD + LEGEND_FIG_TEXT_GAP, legend_y,
                        LEGEND_WIDTH, LEGEND_HEIGHT);
            g2.setColor(Color.black);
            g2.drawRect(legend_box_x + LEGEND_HORI_PAD + LEGEND_FIG_TEXT_GAP, legend_y,
                        LEGEND_WIDTH, LEGEND_HEIGHT);

            legend_y += LEGEND_HEIGHT + INTER_LEGEND_GAP;
        }
    }

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public int getHeight() {
        return mapHEIGHT;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public int getWidth() {
        return mapWIDTH;
    }

    /**
     * Method description
     *
     *
     * @param clone
     *
     * @return
     */
    private Color getCloneColor(final String clone) {

        final int repeatCount = cloneRepeats.get(clone);

        return getCloneColor(repeatCount);
    }

    /**
     * Method description
     *
     *
     * @param repeatCount
     *
     * @return
     */
    private Color getCloneColor(final int repeatCount) {

        switch (repeatCount) {

            case 1 :
                return Color.white;

            case 2 :
                return Color.red;

            case 3 :
                return Color.gray;

            case 4 :
                return Color.yellow;

            case 5 :
                return Color.green;

            case 6 :
                return Color.blue;

            case 7 :
                return Color.cyan;

            default :
                return Color.cyan;
        }
    }

    /**
     * Method description
     *
     *
     * @param length
     *
     * @return
     */
    private int getMapPixelValue(final double length) {
        return (int) (length / (MAX_CH_SIZE_MB * 1000) * MAX_CH_PIX_SIZE);
    }
}
